<template>
    <!-- 背景透明、绝对撑满父级、z-index = 101 -->
    <div ref="root" class="flying-line" />
</template>

<script setup>
/* eslint-disable no-unused-vars */
import { ref, onMounted, onBeforeUnmount, defineExpose, defineEmits, defineProps } from "vue";
import * as THREE from "three";

/* ---------------- props & emit ---------------- */
const props = defineProps({
    duration: { type: Number, required: true },
    ballWidth: { type: Number, required: true },
    speed: { type: Number, required: true },
    showAxis: { type: Boolean, default: false },
});

const emit = defineEmits(["drawStart", "drawEnd"]);

/* ---------- 新增：把画布像素坐标转到裁剪空间 ---------- */
function pixelToClip(pos, canvasSize) {
    return new THREE.Vector2((pos.x / canvasSize.width) * 2, (pos.y / canvasSize.height) * 2);
}

/* ---------------- three 变量 ---------------- */
const root = ref(null);
let scene, camera, renderer;
let animId = null;
let activeLines = new Map(); // id -> { lineMesh, startTime, from, to }

/* ---------- 更新 Shader ---------- */
const vertexShader = `
  uniform float uTime;
  uniform vec2  uFromClip;   // 起点（已转裁剪空间）
  uniform vec2  uToClip;     // 终点
  uniform float uDuration;
  uniform float uSpeed;
  uniform float uBallWidthPx;
  uniform vec2  uResolution; // 画布像素宽高

  varying float vAlpha;
  varying vec2  vUv;

  void main() {
    vUv = uv;

    float t = mod(uTime * uSpeed, uDuration) / uDuration;
    vec2 centerClip = mix(uFromClip, uToClip, t);

    /* 椭圆缩放 */
    float scale = 1.0 - pow(2.0 * t - 1.0, 2.0);
    vec2 ballClip = vec2(uBallWidthPx / uResolution.x * (0.3 + 0.7 * scale),
                         uBallWidthPx / uResolution.y);

    vec2 posClip = centerClip + position.xy * ballClip;
    gl_Position  = vec4(posClip, 0.0, 1.0);

    vAlpha = smoothstep(0.0, 0.2, t) * (1.0 - smoothstep(0.8, 1.0, t));
  }
`;

const fragmentShader = `
  uniform vec3 uColor;
  varying float vAlpha;
  varying vec2  vUv;

  void main() {
    float d = length(vUv - 0.5) * 2.0;
    float a = smoothstep(1.0, 0.3, d) * vAlpha;
    gl_FragColor = vec4(uColor, a);
  }
`;

/* ---------- 创建飞线 ---------- */

function createFlyingLine(from, to, id) {
    if (activeLines.has(id)) return;

    const canvasSize = {
        width: renderer.domElement.width,
        height: renderer.domElement.height,
    };

    const fromClip = pixelToClip(from, canvasSize);
    const toClip = pixelToClip(to, canvasSize);

    /* 让几何体尺寸 = 1×1，后面在 shader 里再乘实际像素大小 */
    const geometry = new THREE.PlaneGeometry(1, 1);
    const material = new THREE.ShaderMaterial({
        uniforms: {
            uTime: { value: 0 },
            uFromClip: { value: fromClip },
            uToClip: { value: toClip },
            uDuration: { value: props.duration },
            uSpeed: { value: props.speed },
            uBallWidthPx: { value: props.ballWidth },
            uResolution: { value: new THREE.Vector2(canvasSize.width, canvasSize.height) },
            uColor: { value: new THREE.Color("#4fc3f7") },
        },
        transparent: true,
        depthTest: false,
        vertexShader,
        fragmentShader,
    });

    const mesh = new THREE.Mesh(geometry, material);
    scene.add(mesh);

    const startTime = Date.now();
    activeLines.set(id, { lineMesh: mesh, startTime, from, to });
    emit("drawStart", id);
}

/* ---------------- 动画循环 ---------------- */
function animate() {
    animId = requestAnimationFrame(animate);

    const now = Date.now();
    activeLines.forEach((obj, id) => {
        const { lineMesh, startTime, from, to } = obj;
        const elapsed = (now - startTime) / 1000; // 秒
        lineMesh.material.uniforms.uTime.value = elapsed;

        if (elapsed >= props.duration) {
            scene.remove(lineMesh);
            lineMesh.geometry.dispose();
            lineMesh.material.dispose();
            activeLines.delete(id);
            emit("drawEnd", { from, to, id });
        }
    });

    renderer.render(scene, camera);
}

/* ---------------- 生命周期 ---------------- */
onMounted(() => {
    const { clientWidth, clientHeight } = root.value.parentElement;

    scene = new THREE.Scene();
    camera = new THREE.OrthographicCamera(-clientWidth / 2, clientWidth / 2, clientHeight / 2, -clientHeight / 2, 0.1, 1000);
    camera.position.z = 1;

    renderer = new THREE.WebGLRenderer({ alpha: true });
    renderer.setSize(clientWidth, clientHeight);
    renderer.setPixelRatio(window.devicePixelRatio);
    root.value.appendChild(renderer.domElement);

    /* 坐标轴 */
    if (props.showAxis) {
        const axes = new THREE.AxesHelper(Math.min(clientWidth, clientHeight) / 2);
        scene.add(axes);
    }

    animate();
});

onBeforeUnmount(() => {
    cancelAnimationFrame(animId);
    renderer?.dispose();
});

/* ---------------- 暴露方法 ---------------- */
defineExpose({ draw: createFlyingLine });
</script>

<style scoped>
.flying-line {
    position: absolute;
    inset: 0;
    z-index: 101;
    pointer-events: none;
}
</style>
